local Class = require('lua.lang.Class')
local Lua = require('lua.vm.Lua')
local Print = require('lua.vm.Print')
local Constants = require('lua.vm.compiler.Constants')

--- @class Instruction : Class
--- @field i number
local Instruction = Class {}

--- @param i number
--- @return Instruction
function Instruction:new(i)
	--- @type Instruction
	local o = {}
	o.i = i
	return setmetatable(o, Instruction)
end

--- @return number
function Instruction:getOpCode()
	return Lua:getOpCode(self.i)
end

--- @return number
function Instruction:getArgA()
	return Lua:getArgA(self.i)
end

--- @param a number
--- @return boolean
function Instruction:setArgA(a)
	local mode = self:getOpMode()
	if mode == Lua.iABC then
		self.i = Constants:createABC(self:getOpCode(), a, self:getArgB(), self:getArgC())
		return true
	elseif mode == Lua.iABx then
		self.i = Constants:createABx(self:getOpCode(), a, self:getArgBx())
		return true
	elseif mode == Lua.iAsBx then
		self.i = Constants:createAbx(self:getOpCode(), a, self:getArgSbx() + 131071)
		return true
	end
	return false
end

--- @param sbx number
--- @return boolean
function Instruction:setArgSbx(sbx)
	if self:getOpMode() == Lua.iAsBx then
		self.i = Constants:createAbx(self:getOpCode(), self:getArgA(), sbx + 131071)
		return true
	end
	return false
end

--- @param op number
--- @return boolean
function Instruction:setOpCode(op)
	local mode = self:getOpMode()
	if mode == Lua.iABC then
		self.i = Constants:createABC(op, self:getArgA(), self:getArgB(), self:getArgC())
		return true
	elseif mode == Lua.iABx then
		self.i = Constants:createABx(op, self:getArgA(), self:getArgBx())
		return true
	elseif mode == Lua.iAsBx then
		self.i = Constants:createAbx(op, self:getArgA(), self:getArgSbx() + 131071)
		return true
	elseif mode == Lua.iAx then
		self.i = Constants:createAx(op, self:getArgAx())
		return true
	end
	return false
end

--- @return number
function Instruction:getArgAx()
	return Lua:getArgAx(self.i)
end

--- @return number
function Instruction:getArgB()
	return Lua:getArgB(self.i)
end

--- @return number
function Instruction:getArgBx()
	return Lua:getArgBx(self.i)
end

--- @return number
function Instruction:getArgSbx()
	return Lua:getArgSbx(self.i)
end

--- @return number
function Instruction:getArgC()
	return Lua:getArgC(self.i)
end

--- @return boolean
function Instruction:isLogic()
	local o = self:getOpCode()
	return o == Lua.OP_EQ or o == Lua.OP_LT or o == Lua.OP_LE or
			o == Lua.OP_TEST or o == Lua.OP_TESTSET
end

--- @return boolean
function Instruction:isReturn()
	local o = self:getOpCode()
	return o == Lua.OP_RETURN or o == Lua.OP_TAILCALL
end

--- @return boolean
function Instruction:isLoop()
	local o = self:getOpCode()
	return o == Lua.OP_JMP or o == Lua.OP_FORLOOP or o == Lua.OP_FORPREP or
			o == Lua.OP_TFORLOOP
end

--- @return boolean
function Instruction:hasOpcode()
	local o = self:getOpCode()
	return o >= Lua.OP_MOVE and o <= Lua.NUM_OPCODES
end

--- @return number
function Instruction:getOpMode()
	return self:hasOpcode() and Lua:getOpMode(self:getOpCode()) or -1
end

--- @return string
function Instruction:getOpName()
	return self:hasOpcode() and Print.OPNAMES[self:getOpCode() + 1] or '?'
end

--- @return string
function Instruction:toString()
	local mode = self:getOpMode()
	return mode == Lua.iABC and
			('%s %s %s %s'):format(self:getOpName(), self:getArgA(), self:getArgB(), self:getArgC())
			or mode == Lua.iABx and
			('%s %s %s'):format(self:getOpName(), self:getArgA(), self:getArgBx())
			or mode == Lua.iAsBx and
			('%s %s %s'):format(self:getOpName(), self:getArgA(), self:getArgSbx())
			or mode == Lua.iAx and
			('%s %s'):format(self:getOpName(), self:getArgAx())
			or ('Instruction(%s)'):format(self.i)
end

return Instruction